local type,tostring,select,pairs,ipairs,assert=type,tostring,select,pairs,ipairs,assert
local string_len,string_rep,string_match,string_format,string_find,string_byte=
	  string.len,string.rep,string.match,string.format,string.find,string.byte
local table_insert,table_remove=
	  table.insert,table.remove
require'zdc.node'

local conf_do = zdc.sandbox.conf_do

module(..., package.seeall)

local NodeTree = {}
for _i,_j in pairs(zdc.node.Node) do
	NodeTree[_i]=_j
end

local NodeTree_mt = {
	__index = function(t,key)
		local i = NodeTree[key] or t._subnode[key]
		if i~=nil then return i end
		
		i=t.__act_v
		if i then
			i=i[key]
			if i~=nil then return i end
		end

		i=t.__inh_v
		if i then
			i=i[key]
			if i~=nil then return i end
		end

		i=t.__raw_v[key]
		if i~=nil then return i end

		if key=='_fds' then return t:__get_fds() end
		if key=='_dfs' then return t:__get_dfs() end
		i=t:__get_field_define(key)
		if i then
			local dyn=i.dyn
			if dyn then
				if type(dyn)~='function' then
					dyn=assert(loadactfun(t.__dpl.dal,t,dyn),'can not load act '..tostring(dyn))
				end
				return dyn(nil,t.__dpl.dal,t,key)
			end
			--if i and i.proto~=false then
			if i.proto then
				return t.__pto_v[key]
			end
			if t.__raw_v._ins then
				return t.__pto_v[key]
			end
			return i and i.v
		end
	end,
	__newindex = function(t,key,nv)
		local dal=t.__dpl.dal
		assert(apiset(nil,dal,t,key,nv))
	end,
	__call = function(t,act,...)
		local dal=t.__dpl.dal
		-- internal call not check acl
		return select(2,assert(apicallact(nil,dal,t,act,...)))
	end,
	__div = function(n,childkey)
		return n:__get_child(childkey)
	end,
}

function new(args)
	local node = setmetatable({__id=args.id,__dpl=args.dpl,
		__needsave=args.firstcreate or false,__needdelsub=false,
		__ik=args.ik or 0,__ts=args.ts or 0,
		__pto_v=false,__raw_v=true,__inh_v=false,__act_v=false,_subnode={}}, NodeTree_mt)
	local rv,err
	if type(args.data)=='table' then
		rv=deflate_node_cp(args.data)
		--rv=args.data
	else
		rv,err=conf_do(args.data,node.__dpl._sb,node.__dpl._log)
		if not rv then return rv,err and err.err end
	end
	
	rawset(node,'__org',node)
	node.__raw_v = rv
	if not args.preload then
		node:__do_inh_act()
		node:__load_child(args.firstcreate)
	end
	return node
end

--override
function NodeTree:__check_nil_fields(deep,helpall)
	local nilf=nil
	for field_name, field in pairs(self._fds) do
		if string_byte(field_name)~=95 then
			local ff=self[field_name]
			if ff==nil then
				nilf=nilf or {}
				nilf[#nilf+1]=field_name
				if not helpall then return nilf end 
			end
		end
	end
	if deep then
		for _i,_j in pairs(self._subnode) do
			local _t=_j:__check_nil_fields(deep,helpall)
			if _t then
				nilf=nilf or {}
				nilf[#nilf+1]=_t
				if not helpall then return nilf end 
			end
		end
	end
	return nilf
end

--base method
function NodeTree:__load_child(firstcreate,prec)
	local _id = self.__id
	if not firstcreate then
		local ns=prec
		local l = #_id+2
		for k,v in pairs(ns) do
			self._subnode[string.sub(k,l)]=v
		end
	end
	-- install fixed fields
	local cfd=self._cfd
	if cfd then
		for _i,_j in pairs(cfd) do
			if not self._subnode[_i] then
				self._subnode[_i] = (not firstcreate and self.__dpl.dal:GetNode(_id .. '/' .. _i)) or self:__make_child(_i,type(_j)=='table' and _j.mc or nil)
			end
		end
	end
end

--override
function NodeTree:__is_proto_val(key)
	local i = self._subnode[key]
	if i~=nil then return false,i end
	if self.__act_v then 
		i=self.__act_v[key]
		if i~=nil then return false,i end
	end
	if self.__inh_v then
		i=self.__inh_v[key]
		if i~=nil then return false,i end
	end
	i=self.__raw_v[key]
	if i~=nil then return false,i end
	return true,self:__get_pto_field(key)
end

--override
function NodeTree:__get_other_fields(of)
	of = of or {}
	local _f = self._fds
	if self.__raw_v then
		for k,_ in pairs(self.__raw_v) do
			if not _f[k] then 
				table_insert(of,k)
			end
		end
	end
	return of
end

--override
function NodeTree:__get_child(key)
	return self._subnode[key]
end

--override
function NodeTree:__sure_child(key,mc,nocache)
	local c=self._subnode[key]
	if c then return c end
	return self.__dpl.dal:SureNode(self.__id .. '/' .. key,mc,nocache)
end

--override
function NodeTree:__get_child_all(_c)
	local _c = _c or {}
	for _i,_j in pairs(self._subnode) do
		_c[_i]=_j
	end
	return _c
end


--override
function NodeTree:__get_child_key_list()
	local _c={}
	for _i,_ in pairs(self._subnode) do
		_c[#_c+1] = _i
	end
	return _c
end

--override
function NodeTree:__remove_child(key,deep)
	self._subnode[key]=nil
	self.__dpl.dal:__remove_ta(self.__id..'/'..key,deep)
end

--override
function NodeTree:__add_child(key,mc,s)
	if self._subnode[key] then return nil, 'ESN1E:'..key end
	--local _t=self.__dpl.dal:GetNode(self.__id .. '/' .. key)
	local _t,errmsg = s
	if not s then
		_t,errmsg=self:__make_child(key,mc)
		if not _t then return nil, errmsg end
	end
	self.__dpl:_put_cache_tree(_t)
	self._subnode[key] = _t
	return _t
end

--base method
function NodeTree:__discard(deep)
end

--base method
function NodeTree:__commit(deep,rec,dbrec)
	return rec,dbrec
end

--override
function NodeTree:__save(deep)
	if self.__needdelsub then
		local tdl=self.__needdelsub
		self.__needdelsub = false
		for _,v in ipairs(tdl) do
			self.__dpl:_remove_node(v.__id,true)
		end
	end
	if self.__needsave then
		self.__needsave = false
		--self:_save()
		self.__dpl:_save_node(self)
	end
	if deep then
		for k,v in pairs(self._subnode) do
			v:__save(deep)
		end
	end
end
